﻿using System;
using System.Net.Security;
using System.Net; //CredentialCache
using System.Security.Principal;

using System.Diagnostics;
using System.Threading;

namespace negoserverjni
{
    public class State : IDisposable
    {
        static TraceSource log = new TraceSource("State");
        PseudoStream stream;
        SavedState header;
        SavedState payload;
        WindowsIdentity identity;
        NegotiateStream negostream;
        AsyncCallback callback1;
        AsyncCallback callback2;
        bool existsServerAuthenticator;
        IAsyncResult param1;
        IAsyncResult param2;
        int lengthRead;

         
        public int LengthRead
        { get { return lengthRead; } }

        public PseudoStream Pseudo
        { set { stream = value; } }

        public NegotiateStream ServerNegoStream
        { get { return negostream; } }

        public AsyncCallback Continuation
        { 
            get 
            {
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "get Continuation");
                AsyncCallback callback = callback1;
                callback1 = callback2;
                callback2 = null;    
                return callback; 
            } 
            set 
            {
                log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "set Continuation");
                if (callback1 == null) callback1 = value;
                else callback2 = value;
            } 
        }
 
        public IAsyncResult ParamContinuation
        { 
            get 
            {
                IAsyncResult param = param1;
                param1 = param2;
                param2 = null;
                return param; 
            } 
            set 
            {
                if (param1 == null) param1 = value;
                else param2 = value;
            }
        }

        public NegotiateStream AuthStream
        { get { return negostream; } }

        public WindowsIdentity Identity
        { get { return identity; } set { identity = value; }  }

        public IAsyncResult Read(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            SavedState savedstate;
            if (count == 5) savedstate = header;
            else savedstate = payload;
            Array.Copy(savedstate.Buf, 0 ,  buffer, offset, count);
            lengthRead = count;
            log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Read : {0}", lengthRead);
            return InsideAsyncResult.Synchronous(state);

        }
            
        public IAsyncResult Write(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
        {
            SavedState s = new SavedState(buffer, offset, count);
            if (count == 5)
            {
                header = s;
                if (s.PayloadLength != 0)
                {
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Write: 5, payloadLength {0}, Synchronous return ", s.PayloadLength);
                    return InsideAsyncResult.Synchronous(state);
                }
                else
                {
                    var iar = InsideAsyncResult.Async(state);
                    Continuation = callback;
                    ParamContinuation = iar;
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Write: after continuation saved: count 5, payloadLength 0, Async return");
                    return iar;
                }
            }
            else
            {
                payload = s;
                if (!existsServerAuthenticator)
                {
                    object authstate = this;
                    negostream = new NegotiateStream(stream);
                    existsServerAuthenticator = true;
                    log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Write: after count {0} saved, before call BeginAuthenticateAsServer", count);

                    negostream.BeginAuthenticateAsServer(CredentialCache.DefaultNetworkCredentials, ProtectionLevel.None, TokenImpersonationLevel.Identification,
                        delegate(IAsyncResult iar )
                        {
                            log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Begin AsyncCallBack of BeginAuthenticateAsServer");
                            State stateLogin = (State)iar.AsyncState;
                            NegotiateStream nego = stateLogin.negostream;
                            try
                            {
                                log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"Before EndAuthenticateAsServer");
                                nego.EndAuthenticateAsServer(iar);
                                if (nego.IsAuthenticated)
                                {
                                    WindowsIdentity identity = (WindowsIdentity)nego.RemoteIdentity;
                                    WindowsPrincipal principal = new WindowsPrincipal(identity);
                                    SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid,null);
                                    if (principal.IsInRole(sid)) stateLogin.identity = identity;
                                    else log.TraceEvent(TraceEventType.Verbose,Thread.CurrentThread.ManagedThreadId,"User is not a Authenticated User");
                                }
                            }
                            catch (Exception e)
                            {
                                State.log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, e.GetType() + ": " + e.Message);
                            }
                        }, authstate);
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Write after call BeginAuthenticateAsServer, count {0}, Synchronous return", count);
                    return InsideAsyncResult.Synchronous(state);
                }
                else
                {
                    var iar = InsideAsyncResult.Async(state);
                    Continuation = callback;
                    ParamContinuation = iar;
                    log.TraceEvent(TraceEventType.Verbose, Thread.CurrentThread.ManagedThreadId, "Write: after count {0} saved, after continuation saved, Async return", count);
                    return iar;
                }

            }
        }

        public void Dispose()
        {
            if (negostream != null)
            {
                negostream.Dispose();
                negostream = null;
            }
            if (stream != null)
            {
                stream.Dispose();
                stream = null;
            }
        }
    }
    public class SavedState
    {
        byte[] buf;
        int count;
        Status status;
        int length;

        public SavedState(byte[] buffer, int offset, int count)
        {
            buf = new byte[count];
            buffer.CopyTo(buf,offset);
            this.count = count;

            if (count == 5)
            {
                Header h = new Header(buf);
                status = h.AuthenticationStatus;
                length = h.Length;
            }
            else
            {
                status = (Status)0;
                length = 0;
            }
        }
        public int PayloadLength
        { get { return length; } }

        public byte[] Buf
        { get { return buf; } }
    }
           
}
